/*!
 * Zdog v1.1.1
 * Round, flat, designer-friendly pseudo-3D engine
 * Licensed MIT
 * https://zzz.dog
 * Copyright 2019 Metafizzy
 */

/**
 * Boilerplate & utils
 */

(function (root, factory) {
    // module definition
    if (typeof module == 'object' && module.exports) {
        // CommonJS
        module.exports = factory();
    } else {
        // browser global
        root.Zdog = factory();
    }
}(this, function factory() {

    var Zdog = {};

    Zdog.TAU = Math.PI * 2;

    Zdog.extend = function (a, b) {
        for (var prop in b) {
            a[prop] = b[prop];
        }
        return a;
    };

    Zdog.lerp = function (a, b, alpha) {
        return (b - a) * alpha + a;
    };

    Zdog.modulo = function (num, div) {
        return ((num % div) + div) % div;
    };

    var powerMultipliers = {
        2: function (a) {
            return a * a;
        },
        3: function (a) {
            return a * a * a;
        },
        4: function (a) {
            return a * a * a * a;
        },
        5: function (a) {
            return a * a * a * a * a;
        },
    };

    Zdog.easeInOut = function (alpha, power) {
        if (power == 1) {
            return alpha;
        }
        alpha = Math.max(0, Math.min(1, alpha));
        var isFirstHalf = alpha < 0.5;
        var slope = isFirstHalf ? alpha : 1 - alpha;
        slope /= 0.5;
        // make easing steeper with more multiples
        var powerMultiplier = powerMultipliers[power] || powerMultipliers[2];
        var curve = powerMultiplier(slope);
        curve /= 2;
        return isFirstHalf ? curve : 1 - curve;
    };

    return Zdog;

}));
/**
 * CanvasRenderer
 */

(function (root, factory) {
    // module definition
    if (typeof module == 'object' && module.exports) {
        // CommonJS
        module.exports = factory();
    } else {
        // browser global
        root.Zdog.CanvasRenderer = factory();
    }
}(this, function factory() {

    var CanvasRenderer = {isCanvas: true};

    CanvasRenderer.begin = function (ctx) {
        ctx.beginPath();
    };

    CanvasRenderer.move = function (ctx, elem, point) {
        ctx.moveTo(point.x, point.y);
    };

    CanvasRenderer.line = function (ctx, elem, point) {
        ctx.lineTo(point.x, point.y);
    };

    CanvasRenderer.bezier = function (ctx, elem, cp0, cp1, end) {
        ctx.bezierCurveTo(cp0.x, cp0.y, cp1.x, cp1.y, end.x, end.y);
    };

    CanvasRenderer.closePath = function (ctx) {
        ctx.closePath();
    };

    CanvasRenderer.setPath = function () {
    };

    CanvasRenderer.renderPath = function (ctx, elem, pathCommands, isClosed) {
        this.begin(ctx, elem);
        pathCommands.forEach(function (command) {
            command.render(ctx, elem, CanvasRenderer);
        });
        if (isClosed) {
            this.closePath(ctx, elem);
        }
    };

    CanvasRenderer.stroke = function (ctx, elem, isStroke, color, lineWidth) {
        if (!isStroke) {
            return;
        }
        ctx.strokeStyle = color;
        ctx.lineWidth = lineWidth;
        ctx.stroke();
    };

    CanvasRenderer.fill = function (ctx, elem, isFill, color) {
        if (!isFill) {
            return;
        }
        ctx.fillStyle = color;
        ctx.fill();
    };

    CanvasRenderer.end = function () {
    };

    return CanvasRenderer;

}));
/**
 * SvgRenderer
 */

(function (root, factory) {
    // module definition
    if (typeof module == 'object' && module.exports) {
        // CommonJS
        module.exports = factory();
    } else {
        // browser global
        root.Zdog.SvgRenderer = factory();
    }
}(this, function factory() {

    var SvgRenderer = {isSvg: true};

// round path coordinates to 3 decimals
    var round = SvgRenderer.round = function (num) {
        return Math.round(num * 1000) / 1000;
    };

    function getPointString(point) {
        return round(point.x) + ',' + round(point.y) + ' ';
    }

    SvgRenderer.begin = function () {
    };

    SvgRenderer.move = function (svg, elem, point) {
        return 'M' + getPointString(point);
    };

    SvgRenderer.line = function (svg, elem, point) {
        return 'L' + getPointString(point);
    };

    SvgRenderer.bezier = function (svg, elem, cp0, cp1, end) {
        return 'C' + getPointString(cp0) + getPointString(cp1) +
            getPointString(end);
    };

    SvgRenderer.closePath = function (/* elem */) {
        return 'Z';
    };

    SvgRenderer.setPath = function (svg, elem, pathValue) {
        elem.setAttribute('d', pathValue);
    };

    SvgRenderer.renderPath = function (svg, elem, pathCommands, isClosed) {
        var pathValue = '';
        pathCommands.forEach(function (command) {
            pathValue += command.render(svg, elem, SvgRenderer);
        });
        if (isClosed) {
            pathValue += this.closePath(svg, elem);
        }
        this.setPath(svg, elem, pathValue);
    };

    SvgRenderer.stroke = function (svg, elem, isStroke, color, lineWidth) {
        if (!isStroke) {
            return;
        }
        elem.setAttribute('stroke', color);
        elem.setAttribute('stroke-width', lineWidth);
    };

    SvgRenderer.fill = function (svg, elem, isFill, color) {
        var fillColor = isFill ? color : 'none';
        elem.setAttribute('fill', fillColor);
    };

    SvgRenderer.end = function (svg, elem) {
        svg.appendChild(elem);
    };

    return SvgRenderer;

}));
/**
 * Vector
 */

(function (root, factory) {
    // module definition
    if (typeof module == 'object' && module.exports) {
        // CommonJS
        module.exports = factory(require('./boilerplate'));
    } else {
        // browser global
        var Zdog = root.Zdog;
        Zdog.Vector = factory(Zdog);
    }

}(this, function factory(utils) {

    function Vector(position) {
        this.set(position);
    }

    var TAU = utils.TAU;

// 'pos' = 'position'
    Vector.prototype.set = function (pos) {
        this.x = pos && pos.x || 0;
        this.y = pos && pos.y || 0;
        this.z = pos && pos.z || 0;
        return this;
    };

// set coordinates without sanitizing
// vec.write({ y: 2 }) only sets y coord
    Vector.prototype.write = function (pos) {
        if (!pos) {
            return this;
        }
        this.x = pos.x != undefined ? pos.x : this.x;
        this.y = pos.y != undefined ? pos.y : this.y;
        this.z = pos.z != undefined ? pos.z : this.z;
        return this;
    };

    Vector.prototype.rotate = function (rotation) {
        if (!rotation) {
            return;
        }
        this.rotateZ(rotation.z);
        this.rotateY(rotation.y);
        this.rotateX(rotation.x);
        return this;
    };

    Vector.prototype.rotateZ = function (angle) {
        rotateProperty(this, angle, 'x', 'y');
    };

    Vector.prototype.rotateX = function (angle) {
        rotateProperty(this, angle, 'y', 'z');
    };

    Vector.prototype.rotateY = function (angle) {
        rotateProperty(this, angle, 'x', 'z');
    };

    function rotateProperty(vec, angle, propA, propB) {
        if (!angle || angle % TAU === 0) {
            return;
        }
        var cos = Math.cos(angle);
        var sin = Math.sin(angle);
        var a = vec[propA];
        var b = vec[propB];
        vec[propA] = a * cos - b * sin;
        vec[propB] = b * cos + a * sin;
    }

    Vector.prototype.isSame = function (pos) {
        if (!pos) {
            return false;
        }
        return this.x === pos.x && this.y === pos.y && this.z === pos.z;
    };

    Vector.prototype.add = function (pos) {
        if (!pos) {
            return this;
        }
        this.x += pos.x || 0;
        this.y += pos.y || 0;
        this.z += pos.z || 0;
        return this;
    };

    Vector.prototype.subtract = function (pos) {
        if (!pos) {
            return this;
        }
        this.x -= pos.x || 0;
        this.y -= pos.y || 0;
        this.z -= pos.z || 0;
        return this;
    };

    Vector.prototype.multiply = function (pos) {
        if (pos == undefined) {
            return this;
        }
        // multiple all values by same number
        if (typeof pos == 'number') {
            this.x *= pos;
            this.y *= pos;
            this.z *= pos;
        } else {
            // multiply object
            this.x *= pos.x != undefined ? pos.x : 1;
            this.y *= pos.y != undefined ? pos.y : 1;
            this.z *= pos.z != undefined ? pos.z : 1;
        }
        return this;
    };

    Vector.prototype.transform = function (translation, rotation, scale) {
        this.multiply(scale);
        this.rotate(rotation);
        this.add(translation);
        return this;
    };

    Vector.prototype.lerp = function (pos, alpha) {
        this.x = utils.lerp(this.x, pos.x || 0, alpha);
        this.y = utils.lerp(this.y, pos.y || 0, alpha);
        this.z = utils.lerp(this.z, pos.z || 0, alpha);
        return this;
    };

    Vector.prototype.magnitude = function () {
        var sum = this.x * this.x + this.y * this.y + this.z * this.z;
        return getMagnitudeSqrt(sum);
    };

    function getMagnitudeSqrt(sum) {
        // PERF: check if sum ~= 1 and skip sqrt
        if (Math.abs(sum - 1) < 0.00000001) {
            return 1;
        }
        return Math.sqrt(sum);
    }

    Vector.prototype.magnitude2d = function () {
        var sum = this.x * this.x + this.y * this.y;
        return getMagnitudeSqrt(sum);
    };

    Vector.prototype.copy = function () {
        return new Vector(this);
    };

    return Vector;

}));
/**
 * Anchor
 */

(function (root, factory) {
    // module definition
    if (typeof module == 'object' && module.exports) {
        // CommonJS
        module.exports = factory(require('./boilerplate'), require('./vector'),
            require('./canvas-renderer'), require('./svg-renderer'));
    } else {
        // browser global
        var Zdog = root.Zdog;
        Zdog.Anchor = factory(Zdog, Zdog.Vector, Zdog.CanvasRenderer,
            Zdog.SvgRenderer);
    }
}(this, function factory(utils, Vector, CanvasRenderer, SvgRenderer) {

    var TAU = utils.TAU;
    var onePoint = {x: 1, y: 1, z: 1};

    function Anchor(options) {
        this.create(options || {});
    }

    Anchor.prototype.create = function (options) {
        this.children = [];
        // set defaults & options
        utils.extend(this, this.constructor.defaults);
        this.setOptions(options);

        // transform
        this.translate = new Vector(options.translate);
        this.rotate = new Vector(options.rotate);
        this.scale = new Vector(onePoint).multiply(this.scale);
        // origin
        this.origin = new Vector();
        this.renderOrigin = new Vector();

        if (this.addTo) {
            this.addTo.addChild(this);
        }
    };

    Anchor.defaults = {};

    Anchor.optionKeys = Object.keys(Anchor.defaults).concat([
        'rotate',
        'translate',
        'scale',
        'addTo',
    ]);

    Anchor.prototype.setOptions = function (options) {
        var optionKeys = this.constructor.optionKeys;

        for (var key in options) {
            if (optionKeys.indexOf(key) != -1) {
                this[key] = options[key];
            }
        }
    };

    Anchor.prototype.addChild = function (shape) {
        if (this.children.indexOf(shape) != -1) {
            return;
        }
        shape.remove(); // remove previous parent
        shape.addTo = this; // keep parent reference
        this.children.push(shape);
    };

    Anchor.prototype.removeChild = function (shape) {
        var index = this.children.indexOf(shape);
        if (index != -1) {
            this.children.splice(index, 1);
        }
    };

    Anchor.prototype.remove = function () {
        if (this.addTo) {
            this.addTo.removeChild(this);
        }
    };

// ----- update ----- //

    Anchor.prototype.update = function () {
        // update self
        this.reset();
        // update children
        this.children.forEach(function (child) {
            child.update();
        });
        this.transform(this.translate, this.rotate, this.scale);
    };

    Anchor.prototype.reset = function () {
        this.renderOrigin.set(this.origin);
    };

    Anchor.prototype.transform = function (translation, rotation, scale) {
        this.renderOrigin.transform(translation, rotation, scale);
        // transform children
        this.children.forEach(function (child) {
            child.transform(translation, rotation, scale);
        });
    };

    Anchor.prototype.updateGraph = function () {
        this.update();
        this.updateFlatGraph();
        this.flatGraph.forEach(function (item) {
            item.updateSortValue();
        });
        // z-sort
        this.flatGraph.sort(Anchor.shapeSorter);
    };

    Anchor.shapeSorter = function (a, b) {
        return a.sortValue - b.sortValue;
    };

// custom getter to check for flatGraph before using it
    Object.defineProperty(Anchor.prototype, 'flatGraph', {
        get: function () {
            if (!this._flatGraph) {
                this.updateFlatGraph();
            }
            return this._flatGraph;
        },
        set: function (graph) {
            this._flatGraph = graph;
        },
    });

    Anchor.prototype.updateFlatGraph = function () {
        this.flatGraph = this.getFlatGraph();
    };

// return Array of self & all child graph items
    Anchor.prototype.getFlatGraph = function () {
        var flatGraph = [this];
        return this.addChildFlatGraph(flatGraph);
    };

    Anchor.prototype.addChildFlatGraph = function (flatGraph) {
        this.children.forEach(function (child) {
            var childFlatGraph = child.getFlatGraph();
            Array.prototype.push.apply(flatGraph, childFlatGraph);
        });
        return flatGraph;
    };

    Anchor.prototype.updateSortValue = function () {
        this.sortValue = this.renderOrigin.z;
    };

// ----- render ----- //

    Anchor.prototype.render = function () {
    };

// TODO refactor out CanvasRenderer so its not a dependency within anchor.js
    Anchor.prototype.renderGraphCanvas = function (ctx) {
        if (!ctx) {
            throw new Error('ctx is ' + ctx + '. ' +
                'Canvas context required for render. Check .renderGraphCanvas( ctx ).');
        }
        this.flatGraph.forEach(function (item) {
            item.render(ctx, CanvasRenderer);
        });
    };

    Anchor.prototype.renderGraphSvg = function (svg) {
        if (!svg) {
            throw new Error('svg is ' + svg + '. ' +
                'SVG required for render. Check .renderGraphSvg( svg ).');
        }
        this.flatGraph.forEach(function (item) {
            item.render(svg, SvgRenderer);
        });
    };

// ----- misc ----- //

    Anchor.prototype.copy = function (options) {
        // copy options
        var itemOptions = {};
        var optionKeys = this.constructor.optionKeys;
        optionKeys.forEach(function (key) {
            itemOptions[key] = this[key];
        }, this);
        // add set options
        utils.extend(itemOptions, options);
        var ItemClass = this.constructor;
        return new ItemClass(itemOptions);
    };

    Anchor.prototype.copyGraph = function (options) {
        var clone = this.copy(options);
        this.children.forEach(function (child) {
            child.copyGraph({
                addTo: clone,
            });
        });
        return clone;
    };

    Anchor.prototype.normalizeRotate = function () {
        this.rotate.x = utils.modulo(this.rotate.x, TAU);
        this.rotate.y = utils.modulo(this.rotate.y, TAU);
        this.rotate.z = utils.modulo(this.rotate.z, TAU);
    };

// ----- subclass ----- //

    function getSubclass(Super) {
        return function (defaults) {
            // create constructor
            function Item(options) {
                this.create(options || {});
            }

            Item.prototype = Object.create(Super.prototype);
            Item.prototype.constructor = Item;

            Item.defaults = utils.extend({}, Super.defaults);
            utils.extend(Item.defaults, defaults);
            // create optionKeys
            Item.optionKeys = Super.optionKeys.slice(0);
            // add defaults keys to optionKeys, dedupe
            Object.keys(Item.defaults).forEach(function (key) {
                if (!Item.optionKeys.indexOf(key) != 1) {
                    Item.optionKeys.push(key);
                }
            });

            Item.subclass = getSubclass(Item);

            return Item;
        };
    }

    Anchor.subclass = getSubclass(Anchor);

    return Anchor;

}));
/**
 * Dragger
 */

(function (root, factory) {
    // module definition
    if (typeof module == 'object' && module.exports) {
        // CommonJS
        module.exports = factory();
    } else {
        // browser global
        root.Zdog.Dragger = factory();
    }
}(this, function factory() {

// quick & dirty drag event stuff
// messes up if multiple pointers/touches

// check for browser window #85
    var hasWindow = typeof window != 'undefined';
// event support, default to mouse events
    var downEvent = 'mousedown';
    var moveEvent = 'mousemove';
    var upEvent = 'mouseup';
    if (hasWindow) {
        if (window.PointerEvent) {
            // PointerEvent, Chrome
            downEvent = 'pointerdown';
            moveEvent = 'pointermove';
            upEvent = 'pointerup';
        } else if ('ontouchstart' in window) {
            // Touch Events, iOS Safari
            downEvent = 'touchstart';
            moveEvent = 'touchmove';
            upEvent = 'touchend';
        }
    }

    function noop() {
    }

    function Dragger(options) {
        this.create(options || {});
    }

    Dragger.prototype.create = function (options) {
        this.onDragStart = options.onDragStart || noop;
        this.onDragMove = options.onDragMove || noop;
        this.onDragEnd = options.onDragEnd || noop;

        this.bindDrag(options.startElement);
    };

    Dragger.prototype.bindDrag = function (element) {
        element = this.getQueryElement(element);
        if (!element) {
            return;
        }
        // disable browser gestures #53
        element.style.touchAction = 'none';
        element.addEventListener(downEvent, this);
    };

    Dragger.prototype.getQueryElement = function (element) {
        if (typeof element == 'string') {
            // with string, query selector
            element = document.querySelector(element);
        }
        return element;
    };

    Dragger.prototype.handleEvent = function (event) {
        var method = this['on' + event.type];
        if (method) {
            method.call(this, event);
        }
    };

    Dragger.prototype.onmousedown =
        Dragger.prototype.onpointerdown = function (event) {
            this.dragStart(event, event);
        };

    Dragger.prototype.ontouchstart = function (event) {
        this.dragStart(event, event.changedTouches[0]);
    };

    Dragger.prototype.dragStart = function (event, pointer) {
        event.preventDefault();
        this.dragStartX = pointer.pageX;
        this.dragStartY = pointer.pageY;
        if (hasWindow) {
            window.addEventListener(moveEvent, this);
            window.addEventListener(upEvent, this);
        }
        this.onDragStart(pointer);
    };

    Dragger.prototype.ontouchmove = function (event) {
        // HACK, moved touch may not be first
        this.dragMove(event, event.changedTouches[0]);
    };

    Dragger.prototype.onmousemove =
        Dragger.prototype.onpointermove = function (event) {
            this.dragMove(event, event);
        };

    Dragger.prototype.dragMove = function (event, pointer) {
        event.preventDefault();
        var moveX = pointer.pageX - this.dragStartX;
        var moveY = pointer.pageY - this.dragStartY;
        this.onDragMove(pointer, moveX, moveY);
    };

    Dragger.prototype.onmouseup =
        Dragger.prototype.onpointerup =
            Dragger.prototype.ontouchend =
                Dragger.prototype.dragEnd = function (/* event */) {
                    window.removeEventListener(moveEvent, this);
                    window.removeEventListener(upEvent, this);
                    this.onDragEnd();
                };

    return Dragger;

}));
/**
 * Illustration
 */

(function (root, factory) {
    // module definition
    if (typeof module == 'object' && module.exports) {
        // CommonJS
        module.exports = factory(require('./boilerplate'), require('./anchor'),
            require('./dragger'));
    } else {
        // browser global
        var Zdog = root.Zdog;
        Zdog.Illustration = factory(Zdog, Zdog.Anchor, Zdog.Dragger);
    }
}(this, function factory(utils, Anchor, Dragger) {

    function noop() {
    }

    var TAU = utils.TAU;

    var Illustration = Anchor.subclass({
        element: undefined,
        centered: true,
        zoom: 1,
        dragRotate: false,
        resize: false,
        onPrerender: noop,
        onDragStart: noop,
        onDragMove: noop,
        onDragEnd: noop,
        onResize: noop,
    });

    utils.extend(Illustration.prototype, Dragger.prototype);

    Illustration.prototype.create = function (options) {
        Anchor.prototype.create.call(this, options);
        Dragger.prototype.create.call(this, options);
        this.setElement(this.element);
        this.setDragRotate(this.dragRotate);
        this.setResize(this.resize);
    };

    Illustration.prototype.setElement = function (element) {
        element = this.getQueryElement(element);
        if (!element) {
            throw new Error('Zdog.Illustration element required. Set to ' + element);
        }

        var nodeName = element.nodeName.toLowerCase();
        if (nodeName == 'canvas') {
            this.setCanvas(element);
        } else if (nodeName == 'svg') {
            this.setSvg(element);
        }
    };

    Illustration.prototype.setSize = function (width, height) {
        width = Math.round(width);
        height = Math.round(height);
        if (this.isCanvas) {
            this.setSizeCanvas(width, height);
        } else if (this.isSvg) {
            this.setSizeSvg(width, height);
        }
    };

    Illustration.prototype.setResize = function (resize) {
        this.resize = resize;
        // create resize event listener
        if (!this.resizeListener) {
            this.resizeListener = this.onWindowResize.bind(this);
        }
        // add/remove event listener
        if (resize) {
            window.addEventListener('resize', this.resizeListener);
            this.onWindowResize();
        } else {
            window.removeEventListener('resize', this.resizeListener);
        }
    };

// TODO debounce this?
    Illustration.prototype.onWindowResize = function () {
        this.setMeasuredSize();
        this.onResize(this.width, this.height);
    };

    Illustration.prototype.setMeasuredSize = function () {
        var width, height;
        var isFullscreen = this.resize == 'fullscreen';
        if (isFullscreen) {
            width = window.innerWidth;
            height = window.innerHeight;
        } else {
            var rect = this.element.getBoundingClientRect();
            width = rect.width;
            height = rect.height;
        }
        this.setSize(width, height);
    };

// ----- render ----- //

    Illustration.prototype.renderGraph = function (item) {
        if (this.isCanvas) {
            this.renderGraphCanvas(item);
        } else if (this.isSvg) {
            this.renderGraphSvg(item);
        }
    };

// combo method
    Illustration.prototype.updateRenderGraph = function (item) {
        this.updateGraph();
        this.renderGraph(item);
    };

// ----- canvas ----- //

    Illustration.prototype.setCanvas = function (element) {
        this.element = element;
        this.isCanvas = true;
        // update related properties
        this.ctx = this.element.getContext('2d');
        // set initial size
        this.setSizeCanvas(element.width, element.height);
    };

    Illustration.prototype.setSizeCanvas = function (width, height) {
        this.width = width;
        this.height = height;
        // up-rez for hi-DPI devices
        var pixelRatio = this.pixelRatio = window.devicePixelRatio || 1;
        this.element.width = this.canvasWidth = width * pixelRatio;
        this.element.height = this.canvasHeight = height * pixelRatio;
        var needsHighPixelRatioSizing = pixelRatio > 1 && !this.resize;
        if (needsHighPixelRatioSizing) {
            this.element.style.width = width + 'px';
            this.element.style.height = height + 'px';
        }
    };

    Illustration.prototype.renderGraphCanvas = function (item) {
        item = item || this;
        this.prerenderCanvas();
        Anchor.prototype.renderGraphCanvas.call(item, this.ctx);
        this.postrenderCanvas();
    };

    Illustration.prototype.prerenderCanvas = function () {
        var ctx = this.ctx;
        ctx.lineCap = 'round';
        ctx.lineJoin = 'round';
        ctx.clearRect(0, 0, this.canvasWidth, this.canvasHeight);
        ctx.save();
        if (this.centered) {
            var centerX = this.width / 2 * this.pixelRatio;
            var centerY = this.height / 2 * this.pixelRatio;
            ctx.translate(centerX, centerY);
        }
        var scale = this.pixelRatio * this.zoom;
        ctx.scale(scale, scale);
        this.onPrerender(ctx);
    };

    Illustration.prototype.postrenderCanvas = function () {
        this.ctx.restore();
    };

// ----- svg ----- //

    Illustration.prototype.setSvg = function (element) {
        this.element = element;
        this.isSvg = true;
        this.pixelRatio = 1;
        // set initial size from width & height attributes
        var width = element.getAttribute('width');
        var height = element.getAttribute('height');
        this.setSizeSvg(width, height);
    };

    Illustration.prototype.setSizeSvg = function (width, height) {
        this.width = width;
        this.height = height;
        var viewWidth = width / this.zoom;
        var viewHeight = height / this.zoom;
        var viewX = this.centered ? -viewWidth / 2 : 0;
        var viewY = this.centered ? -viewHeight / 2 : 0;
        this.element.setAttribute('viewBox', viewX + ' ' + viewY + ' ' +
            viewWidth + ' ' + viewHeight);
        if (this.resize) {
            // remove size attributes, let size be determined by viewbox
            this.element.removeAttribute('width');
            this.element.removeAttribute('height');
        } else {
            this.element.setAttribute('width', width);
            this.element.setAttribute('height', height);
        }
    };

    Illustration.prototype.renderGraphSvg = function (item) {
        item = item || this;
        empty(this.element);
        this.onPrerender(this.element);
        Anchor.prototype.renderGraphSvg.call(item, this.element);
    };

    function empty(element) {
        while (element.firstChild) {
            element.removeChild(element.firstChild);
        }
    }

// ----- drag ----- //

    Illustration.prototype.setDragRotate = function (item) {
        if (!item) {
            return;
        } else if (item === true) {
            /* eslint consistent-this: "off" */
            item = this;
        }
        this.dragRotate = item;

        this.bindDrag(this.element);
    };

    Illustration.prototype.dragStart = function (/* event, pointer */) {
        this.dragStartRX = this.dragRotate.rotate.x;
        this.dragStartRY = this.dragRotate.rotate.y;
        Dragger.prototype.dragStart.apply(this, arguments);
    };

    Illustration.prototype.dragMove = function (event, pointer) {
        var moveX = pointer.pageX - this.dragStartX;
        var moveY = pointer.pageY - this.dragStartY;
        var displaySize = Math.min(this.width, this.height);
        var moveRY = moveX / displaySize * TAU;
        var moveRX = moveY / displaySize * TAU;
        this.dragRotate.rotate.x = this.dragStartRX - moveRX;
        this.dragRotate.rotate.y = this.dragStartRY - moveRY;
        Dragger.prototype.dragMove.apply(this, arguments);
    };

    return Illustration;

}));
/**
 * PathCommand
 */

(function (root, factory) {
    // module definition
    if (typeof module == 'object' && module.exports) {
        // CommonJS
        module.exports = factory(require('./vector'));
    } else {
        // browser global
        var Zdog = root.Zdog;
        Zdog.PathCommand = factory(Zdog.Vector);
    }
}(this, function factory(Vector) {

    function PathCommand(method, points, previousPoint) {
        this.method = method;
        this.points = points.map(mapVectorPoint);
        this.renderPoints = points.map(mapNewVector);
        this.previousPoint = previousPoint;
        this.endRenderPoint = this.renderPoints[this.renderPoints.length - 1];
        // arc actions come with previous point & corner point
        // but require bezier control points
        if (method == 'arc') {
            this.controlPoints = [new Vector(), new Vector()];
        }
    }

    function mapVectorPoint(point) {
        if (point instanceof Vector) {
            return point;
        } else {
            return new Vector(point);
        }
    }

    function mapNewVector(point) {
        return new Vector(point);
    }

    PathCommand.prototype.reset = function () {
        // reset renderPoints back to orignal points position
        var points = this.points;
        this.renderPoints.forEach(function (renderPoint, i) {
            var point = points[i];
            renderPoint.set(point);
        });
    };

    PathCommand.prototype.transform = function (translation, rotation, scale) {
        this.renderPoints.forEach(function (renderPoint) {
            renderPoint.transform(translation, rotation, scale);
        });
    };

    PathCommand.prototype.render = function (ctx, elem, renderer) {
        return this[this.method](ctx, elem, renderer);
    };

    PathCommand.prototype.move = function (ctx, elem, renderer) {
        return renderer.move(ctx, elem, this.renderPoints[0]);
    };

    PathCommand.prototype.line = function (ctx, elem, renderer) {
        return renderer.line(ctx, elem, this.renderPoints[0]);
    };

    PathCommand.prototype.bezier = function (ctx, elem, renderer) {
        var cp0 = this.renderPoints[0];
        var cp1 = this.renderPoints[1];
        var end = this.renderPoints[2];
        return renderer.bezier(ctx, elem, cp0, cp1, end);
    };

    var arcHandleLength = 9 / 16;

    PathCommand.prototype.arc = function (ctx, elem, renderer) {
        var prev = this.previousPoint;
        var corner = this.renderPoints[0];
        var end = this.renderPoints[1];
        var cp0 = this.controlPoints[0];
        var cp1 = this.controlPoints[1];
        cp0.set(prev).lerp(corner, arcHandleLength);
        cp1.set(end).lerp(corner, arcHandleLength);
        return renderer.bezier(ctx, elem, cp0, cp1, end);
    };

    return PathCommand;

}));
/**
 * Shape
 */

(function (root, factory) {
    // module definition
    if (typeof module == 'object' && module.exports) {
        // CommonJS
        module.exports = factory(require('./boilerplate'), require('./vector'),
            require('./path-command'), require('./anchor'));
    } else {
        // browser global
        var Zdog = root.Zdog;
        Zdog.Shape = factory(Zdog, Zdog.Vector, Zdog.PathCommand, Zdog.Anchor);
    }
}(this, function factory(utils, Vector, PathCommand, Anchor) {

    var Shape = Anchor.subclass({
        stroke: 1,
        fill: false,
        color: '#333',
        closed: true,
        visible: true,
        path: [{}],
        front: {z: 1},
        backface: true,
    });

    Shape.prototype.create = function (options) {
        Anchor.prototype.create.call(this, options);
        this.updatePath();
        // front
        this.front = new Vector(options.front || this.front);
        this.renderFront = new Vector(this.front);
        this.renderNormal = new Vector();
    };

    var actionNames = [
        'move',
        'line',
        'bezier',
        'arc',
    ];

    Shape.prototype.updatePath = function () {
        this.setPath();
        this.updatePathCommands();
    };

// place holder for Ellipse, Rect, etc.
    Shape.prototype.setPath = function () {
    };

// parse path into PathCommands
    Shape.prototype.updatePathCommands = function () {
        var previousPoint;
        this.pathCommands = this.path.map(function (pathPart, i) {
            // pathPart can be just vector coordinates -> { x, y, z }
            // or path instruction -> { arc: [ {x0,y0,z0}, {x1,y1,z1} ] }
            var keys = Object.keys(pathPart);
            var method = keys[0];
            var points = pathPart[method];
            // default to line if no instruction
            var isInstruction = keys.length == 1 && actionNames.indexOf(method) != -1;
            if (!isInstruction) {
                method = 'line';
                points = pathPart;
            }
            // munge single-point methods like line & move without arrays
            var isLineOrMove = method == 'line' || method == 'move';
            var isPointsArray = Array.isArray(points);
            if (isLineOrMove && !isPointsArray) {
                points = [points];
            }

            // first action is always move
            method = i === 0 ? 'move' : method;
            // arcs require previous last point
            var command = new PathCommand(method, points, previousPoint);
            // update previousLastPoint
            previousPoint = command.endRenderPoint;
            return command;
        });
    };

// ----- update ----- //

    Shape.prototype.reset = function () {
        this.renderOrigin.set(this.origin);
        this.renderFront.set(this.front);
        // reset command render points
        this.pathCommands.forEach(function (command) {
            command.reset();
        });
    };

    Shape.prototype.transform = function (translation, rotation, scale) {
        // calculate render points backface visibility & cone/hemisphere shapes
        this.renderOrigin.transform(translation, rotation, scale);
        this.renderFront.transform(translation, rotation, scale);
        this.renderNormal.set(this.renderOrigin).subtract(this.renderFront);
        // transform points
        this.pathCommands.forEach(function (command) {
            command.transform(translation, rotation, scale);
        });
        // transform children
        this.children.forEach(function (child) {
            child.transform(translation, rotation, scale);
        });
    };

    Shape.prototype.updateSortValue = function () {
        // sort by average z of all points
        // def not geometrically correct, but works for me
        var pointCount = this.pathCommands.length;
        var firstPoint = this.pathCommands[0].endRenderPoint;
        var lastPoint = this.pathCommands[pointCount - 1].endRenderPoint;
        // ignore the final point if self closing shape
        var isSelfClosing = pointCount > 2 && firstPoint.isSame(lastPoint);
        if (isSelfClosing) {
            pointCount -= 1;
        }

        var sortValueTotal = 0;
        for (var i = 0; i < pointCount; i++) {
            sortValueTotal += this.pathCommands[i].endRenderPoint.z;
        }
        this.sortValue = sortValueTotal / pointCount;
    };

// ----- render ----- //

    Shape.prototype.render = function (ctx, renderer) {
        var length = this.pathCommands.length;
        if (!this.visible || !length) {
            return;
        }
        // do not render if hiding backface
        this.isFacingBack = this.renderNormal.z > 0;
        if (!this.backface && this.isFacingBack) {
            return;
        }
        if (!renderer) {
            throw new Error('Zdog renderer required. Set to ' + renderer);
        }
        // render dot or path
        var isDot = length == 1;
        if (renderer.isCanvas && isDot) {
            this.renderCanvasDot(ctx, renderer);
        } else {
            this.renderPath(ctx, renderer);
        }
    };

    var TAU = utils.TAU;
// Safari does not render lines with no size, have to render circle instead
    Shape.prototype.renderCanvasDot = function (ctx) {
        var lineWidth = this.getLineWidth();
        if (!lineWidth) {
            return;
        }
        ctx.fillStyle = this.getRenderColor();
        var point = this.pathCommands[0].endRenderPoint;
        ctx.beginPath();
        var radius = lineWidth / 2;
        ctx.arc(point.x, point.y, radius, 0, TAU);
        ctx.fill();
    };

    Shape.prototype.getLineWidth = function () {
        if (!this.stroke) {
            return 0;
        }
        if (this.stroke == true) {
            return 1;
        }
        return this.stroke;
    };

    Shape.prototype.getRenderColor = function () {
        // use backface color if applicable
        var isBackfaceColor = typeof this.backface == 'string' && this.isFacingBack;
        var color = isBackfaceColor ? this.backface : this.color;
        return color;
    };

    Shape.prototype.renderPath = function (ctx, renderer) {
        var elem = this.getRenderElement(ctx, renderer);
        var isTwoPoints = this.pathCommands.length == 2 &&
            this.pathCommands[1].method == 'line';
        var isClosed = !isTwoPoints && this.closed;
        var color = this.getRenderColor();

        renderer.renderPath(ctx, elem, this.pathCommands, isClosed);
        renderer.stroke(ctx, elem, this.stroke, color, this.getLineWidth());
        renderer.fill(ctx, elem, this.fill, color);
        renderer.end(ctx, elem);
    };

    var svgURI = 'http://www.w3.org/2000/svg';

    Shape.prototype.getRenderElement = function (ctx, renderer) {
        if (!renderer.isSvg) {
            return;
        }
        if (!this.svgElement) {
            // create svgElement
            this.svgElement = document.createElementNS(svgURI, 'path');
            this.svgElement.setAttribute('stroke-linecap', 'round');
            this.svgElement.setAttribute('stroke-linejoin', 'round');
        }
        return this.svgElement;
    };

    return Shape;

}));
/**
 * Group
 */

(function (root, factory) {
    // module definition
    if (typeof module == 'object' && module.exports) {
        // CommonJS
        module.exports = factory(require('./anchor'));
    } else {
        // browser global
        var Zdog = root.Zdog;
        Zdog.Group = factory(Zdog.Anchor);
    }
}(this, function factory(Anchor) {

    var Group = Anchor.subclass({
        updateSort: false,
        visible: true,
    });

// ----- update ----- //

    Group.prototype.updateSortValue = function () {
        var sortValueTotal = 0;
        this.flatGraph.forEach(function (item) {
            item.updateSortValue();
            sortValueTotal += item.sortValue;
        });
        // average sort value of all points
        // def not geometrically correct, but works for me
        this.sortValue = sortValueTotal / this.flatGraph.length;

        if (this.updateSort) {
            this.flatGraph.sort(Anchor.shapeSorter);
        }
    };

// ----- render ----- //

    Group.prototype.render = function (ctx, renderer) {
        if (!this.visible) {
            return;
        }

        this.flatGraph.forEach(function (item) {
            item.render(ctx, renderer);
        });
    };

// actual group flatGraph only used inside group
    Group.prototype.updateFlatGraph = function () {
        // do not include self
        var flatGraph = [];
        this.flatGraph = this.addChildFlatGraph(flatGraph);
    };

// do not include children, group handles rendering & sorting internally
    Group.prototype.getFlatGraph = function () {
        return [this];
    };

    return Group;

}));
/**
 * Rect
 */

(function (root, factory) {
    // module definition
    if (typeof module == 'object' && module.exports) {
        // CommonJS
        module.exports = factory(require('./shape'));
    } else {
        // browser global
        var Zdog = root.Zdog;
        Zdog.Rect = factory(Zdog.Shape);
    }
}(this, function factory(Shape) {

    var Rect = Shape.subclass({
        width: 1,
        height: 1,
    });

    Rect.prototype.setPath = function () {
        var x = this.width / 2;
        var y = this.height / 2;
        /* eslint key-spacing: "off" */
        this.path = [
            {x: -x, y: -y},
            {x: x, y: -y},
            {x: x, y: y},
            {x: -x, y: y},
        ];
    };

    return Rect;

}));
/**
 * RoundedRect
 */

(function (root, factory) {
    // module definition
    if (typeof module == 'object' && module.exports) {
        // CommonJS
        module.exports = factory(require('./shape'));
    } else {
        // browser global
        var Zdog = root.Zdog;
        Zdog.RoundedRect = factory(Zdog.Shape);
    }
}(this, function factory(Shape) {

    var RoundedRect = Shape.subclass({
        width: 1,
        height: 1,
        cornerRadius: 0.25,
        closed: false,
    });

    RoundedRect.prototype.setPath = function () {
        /* eslint
           id-length: [ "error", { "min": 2, "exceptions": [ "x", "y" ] }],
           key-spacing: "off" */
        var xA = this.width / 2;
        var yA = this.height / 2;
        var shortSide = Math.min(xA, yA);
        var cornerRadius = Math.min(this.cornerRadius, shortSide);
        var xB = xA - cornerRadius;
        var yB = yA - cornerRadius;
        var path = [
            // top right corner
            {x: xB, y: -yA},
            {
                arc: [
                    {x: xA, y: -yA},
                    {x: xA, y: -yB},
                ]
            },
        ];
        // bottom right corner
        if (yB) {
            path.push({x: xA, y: yB});
        }
        path.push({
            arc: [
                {x: xA, y: yA},
                {x: xB, y: yA},
            ]
        });
        // bottom left corner
        if (xB) {
            path.push({x: -xB, y: yA});
        }
        path.push({
            arc: [
                {x: -xA, y: yA},
                {x: -xA, y: yB},
            ]
        });
        // top left corner
        if (yB) {
            path.push({x: -xA, y: -yB});
        }
        path.push({
            arc: [
                {x: -xA, y: -yA},
                {x: -xB, y: -yA},
            ]
        });

        // back to top right corner
        if (xB) {
            path.push({x: xB, y: -yA});
        }

        this.path = path;
    };

    return RoundedRect;

}));
/**
 * Ellipse
 */

(function (root, factory) {
    // module definition
    if (typeof module == 'object' && module.exports) {
        // CommonJS
        module.exports = factory(require('./shape'));
    } else {
        // browser global
        var Zdog = root.Zdog;
        Zdog.Ellipse = factory(Zdog.Shape);
    }

}(this, function factory(Shape) {

    var Ellipse = Shape.subclass({
        diameter: 1,
        width: undefined,
        height: undefined,
        quarters: 4,
        closed: false,
    });

    Ellipse.prototype.setPath = function () {
        var width = this.width != undefined ? this.width : this.diameter;
        var height = this.height != undefined ? this.height : this.diameter;
        var x = width / 2;
        var y = height / 2;
        this.path = [
            {x: 0, y: -y},
            {
                arc: [ // top right
                    {x: x, y: -y},
                    {x: x, y: 0},
                ]
            },
        ];
        // bottom right
        if (this.quarters > 1) {
            this.path.push({
                arc: [
                    {x: x, y: y},
                    {x: 0, y: y},
                ]
            });
        }
        // bottom left
        if (this.quarters > 2) {
            this.path.push({
                arc: [
                    {x: -x, y: y},
                    {x: -x, y: 0},
                ]
            });
        }
        // top left
        if (this.quarters > 3) {
            this.path.push({
                arc: [
                    {x: -x, y: -y},
                    {x: 0, y: -y},
                ]
            });
        }
    };

    return Ellipse;

}));
/**
 * Shape
 */

(function (root, factory) {
    // module definition
    if (typeof module == 'object' && module.exports) {
        // CommonJS
        module.exports = factory(require('./boilerplate'), require('./shape'));
    } else {
        // browser global
        var Zdog = root.Zdog;
        Zdog.Polygon = factory(Zdog, Zdog.Shape);
    }
}(this, function factory(utils, Shape) {

    var Polygon = Shape.subclass({
        sides: 3,
        radius: 0.5,
    });

    var TAU = utils.TAU;

    Polygon.prototype.setPath = function () {
        this.path = [];
        for (var i = 0; i < this.sides; i++) {
            var theta = i / this.sides * TAU - TAU / 4;
            var x = Math.cos(theta) * this.radius;
            var y = Math.sin(theta) * this.radius;
            this.path.push({x: x, y: y});
        }
    };

    return Polygon;

}));
/**
 * Hemisphere composite shape
 */

(function (root, factory) {
    // module definition
    if (typeof module == 'object' && module.exports) {
        // CommonJS
        module.exports = factory(require('./boilerplate'), require('./vector'),
            require('./anchor'), require('./ellipse'));
    } else {
        // browser global
        var Zdog = root.Zdog;
        Zdog.Hemisphere = factory(Zdog, Zdog.Vector, Zdog.Anchor, Zdog.Ellipse);
    }
}(this, function factory(utils, Vector, Anchor, Ellipse) {

    var Hemisphere = Ellipse.subclass({
        fill: true,
    });

    var TAU = utils.TAU;

    Hemisphere.prototype.create = function (/* options */) {
        // call super
        Ellipse.prototype.create.apply(this, arguments);
        // composite shape, create child shapes
        this.apex = new Anchor({
            addTo: this,
            translate: {z: this.diameter / 2},
        });
        // vector used for calculation
        this.renderCentroid = new Vector();
    };

    Hemisphere.prototype.updateSortValue = function () {
        // centroid of hemisphere is 3/8 between origin and apex
        this.renderCentroid.set(this.renderOrigin)
            .lerp(this.apex.renderOrigin, 3 / 8);
        this.sortValue = this.renderCentroid.z;
    };

    Hemisphere.prototype.render = function (ctx, renderer) {
        this.renderDome(ctx, renderer);
        // call super
        Ellipse.prototype.render.apply(this, arguments);
    };

    Hemisphere.prototype.renderDome = function (ctx, renderer) {
        if (!this.visible) {
            return;
        }
        var elem = this.getDomeRenderElement(ctx, renderer);
        var contourAngle = Math.atan2(this.renderNormal.y, this.renderNormal.x);
        var domeRadius = this.diameter / 2 * this.renderNormal.magnitude();
        var x = this.renderOrigin.x;
        var y = this.renderOrigin.y;

        if (renderer.isCanvas) {
            // canvas
            var startAngle = contourAngle + TAU / 4;
            var endAngle = contourAngle - TAU / 4;
            ctx.beginPath();
            ctx.arc(x, y, domeRadius, startAngle, endAngle);
        } else if (renderer.isSvg) {
            // svg
            contourAngle = (contourAngle - TAU / 4) / TAU * 360;
            this.domeSvgElement.setAttribute('d', 'M ' + -domeRadius + ',0 A ' +
                domeRadius + ',' + domeRadius + ' 0 0 1 ' + domeRadius + ',0');
            this.domeSvgElement.setAttribute('transform',
                'translate(' + x + ',' + y + ' ) rotate(' + contourAngle + ')');
        }

        renderer.stroke(ctx, elem, this.stroke, this.color, this.getLineWidth());
        renderer.fill(ctx, elem, this.fill, this.color);
        renderer.end(ctx, elem);
    };

    var svgURI = 'http://www.w3.org/2000/svg';

    Hemisphere.prototype.getDomeRenderElement = function (ctx, renderer) {
        if (!renderer.isSvg) {
            return;
        }
        if (!this.domeSvgElement) {
            // create svgElement
            this.domeSvgElement = document.createElementNS(svgURI, 'path');
            this.domeSvgElement.setAttribute('stroke-linecap', 'round');
            this.domeSvgElement.setAttribute('stroke-linejoin', 'round');
        }
        return this.domeSvgElement;
    };

    return Hemisphere;

}));
/**
 * Cylinder composite shape
 */

(function (root, factory) {
    // module definition
    if (typeof module == 'object' && module.exports) {
        // CommonJS
        module.exports = factory(require('./boilerplate'),
            require('./path-command'), require('./shape'), require('./group'),
            require('./ellipse'));
    } else {
        // browser global
        var Zdog = root.Zdog;
        Zdog.Cylinder = factory(Zdog, Zdog.PathCommand, Zdog.Shape,
            Zdog.Group, Zdog.Ellipse);
    }
}(this, function factory(utils, PathCommand, Shape, Group, Ellipse) {

    function noop() {
    }

// ----- CylinderGroup ----- //

    var CylinderGroup = Group.subclass({
        color: '#333',
        updateSort: true,
    });

    CylinderGroup.prototype.create = function () {
        Group.prototype.create.apply(this, arguments);
        this.pathCommands = [
            new PathCommand('move', [{}]),
            new PathCommand('line', [{}]),
        ];
    };

    CylinderGroup.prototype.render = function (ctx, renderer) {
        this.renderCylinderSurface(ctx, renderer);
        Group.prototype.render.apply(this, arguments);
    };

    CylinderGroup.prototype.renderCylinderSurface = function (ctx, renderer) {
        if (!this.visible) {
            return;
        }
        // render cylinder surface
        var elem = this.getRenderElement(ctx, renderer);
        var frontBase = this.frontBase;
        var rearBase = this.rearBase;
        var scale = frontBase.renderNormal.magnitude();
        var strokeWidth = frontBase.diameter * scale + frontBase.getLineWidth();
        // set path command render points
        this.pathCommands[0].renderPoints[0].set(frontBase.renderOrigin);
        this.pathCommands[1].renderPoints[0].set(rearBase.renderOrigin);

        if (renderer.isCanvas) {
            ctx.lineCap = 'butt'; // nice
        }
        renderer.renderPath(ctx, elem, this.pathCommands);
        renderer.stroke(ctx, elem, true, this.color, strokeWidth);
        renderer.end(ctx, elem);

        if (renderer.isCanvas) {
            ctx.lineCap = 'round'; // reset
        }
    };

    var svgURI = 'http://www.w3.org/2000/svg';

    CylinderGroup.prototype.getRenderElement = function (ctx, renderer) {
        if (!renderer.isSvg) {
            return;
        }
        if (!this.svgElement) {
            // create svgElement
            this.svgElement = document.createElementNS(svgURI, 'path');
        }
        return this.svgElement;
    };

// prevent double-creation in parent.copyGraph()
// only create in Cylinder.create()
    CylinderGroup.prototype.copyGraph = noop;

// ----- CylinderEllipse ----- //

    var CylinderEllipse = Ellipse.subclass();

    CylinderEllipse.prototype.copyGraph = noop;

// ----- Cylinder ----- //

    var Cylinder = Shape.subclass({
        diameter: 1,
        length: 1,
        frontFace: undefined,
        fill: true,
    });

    var TAU = utils.TAU;

    Cylinder.prototype.create = function (/* options */) {
        // call super
        Shape.prototype.create.apply(this, arguments);
        // composite shape, create child shapes
        // CylinderGroup to render cylinder surface then bases
        this.group = new CylinderGroup({
            addTo: this,
            color: this.color,
            visible: this.visible,
        });
        var baseZ = this.length / 2;
        var baseColor = this.backface || true;
        // front outside base
        this.frontBase = this.group.frontBase = new Ellipse({
            addTo: this.group,
            diameter: this.diameter,
            translate: {z: baseZ},
            rotate: {y: TAU / 2},
            color: this.color,
            stroke: this.stroke,
            fill: this.fill,
            backface: this.frontFace || baseColor,
            visible: this.visible,
        });
        // back outside base
        this.rearBase = this.group.rearBase = this.frontBase.copy({
            translate: {z: -baseZ},
            rotate: {y: 0},
            backface: baseColor,
        });
    };

// Cylinder shape does not render anything
    Cylinder.prototype.render = function () {
    };

// ----- set child properties ----- //

    var childProperties = ['stroke', 'fill', 'color', 'visible'];
    childProperties.forEach(function (property) {
        // use proxy property for custom getter & setter
        var _prop = '_' + property;
        Object.defineProperty(Cylinder.prototype, property, {
            get: function () {
                return this[_prop];
            },
            set: function (value) {
                this[_prop] = value;
                // set property on children
                if (this.frontBase) {
                    this.frontBase[property] = value;
                    this.rearBase[property] = value;
                    this.group[property] = value;
                }
            },
        });
    });

// TODO child property setter for backface, frontBaseColor, & rearBaseColor

    return Cylinder;

}));
/**
 * Cone composite shape
 */

(function (root, factory) {
    // module definition
    if (typeof module == 'object' && module.exports) {
        // CommonJS
        module.exports = factory(require('./boilerplate'), require('./vector'),
            require('./path-command'), require('./anchor'), require('./ellipse'));
    } else {
        // browser global
        var Zdog = root.Zdog;
        Zdog.Cone = factory(Zdog, Zdog.Vector, Zdog.PathCommand,
            Zdog.Anchor, Zdog.Ellipse);
    }
}(this, function factory(utils, Vector, PathCommand, Anchor, Ellipse) {

    var Cone = Ellipse.subclass({
        length: 1,
        fill: true,
    });

    var TAU = utils.TAU;

    Cone.prototype.create = function (/* options */) {
        // call super
        Ellipse.prototype.create.apply(this, arguments);
        // composite shape, create child shapes
        this.apex = new Anchor({
            addTo: this,
            translate: {z: this.length},
        });

        // vectors used for calculation
        this.renderApex = new Vector();
        this.renderCentroid = new Vector();
        this.tangentA = new Vector();
        this.tangentB = new Vector();

        this.surfacePathCommands = [
            new PathCommand('move', [{}]), // points set in renderConeSurface
            new PathCommand('line', [{}]),
            new PathCommand('line', [{}]),
        ];
    };

    Cone.prototype.updateSortValue = function () {
        // center of cone is one third of its length
        this.renderCentroid.set(this.renderOrigin)
            .lerp(this.apex.renderOrigin, 1 / 3);
        this.sortValue = this.renderCentroid.z;
    };

    Cone.prototype.render = function (ctx, renderer) {
        this.renderConeSurface(ctx, renderer);
        Ellipse.prototype.render.apply(this, arguments);
    };

    Cone.prototype.renderConeSurface = function (ctx, renderer) {
        if (!this.visible) {
            return;
        }
        this.renderApex.set(this.apex.renderOrigin)
            .subtract(this.renderOrigin);

        var scale = this.renderNormal.magnitude();
        var apexDistance = this.renderApex.magnitude2d();
        var normalDistance = this.renderNormal.magnitude2d();
        // eccentricity
        var eccenAngle = Math.acos(normalDistance / scale);
        var eccen = Math.sin(eccenAngle);
        var radius = this.diameter / 2 * scale;
        // does apex extend beyond eclipse of face
        var isApexVisible = radius * eccen < apexDistance;
        if (!isApexVisible) {
            return;
        }
        // update tangents
        var apexAngle = Math.atan2(this.renderNormal.y, this.renderNormal.x) +
            TAU / 2;
        var projectLength = apexDistance / eccen;
        var projectAngle = Math.acos(radius / projectLength);
        // set tangent points
        var tangentA = this.tangentA;
        var tangentB = this.tangentB;

        tangentA.x = Math.cos(projectAngle) * radius * eccen;
        tangentA.y = Math.sin(projectAngle) * radius;

        tangentB.set(this.tangentA);
        tangentB.y *= -1;

        tangentA.rotateZ(apexAngle);
        tangentB.rotateZ(apexAngle);
        tangentA.add(this.renderOrigin);
        tangentB.add(this.renderOrigin);

        this.setSurfaceRenderPoint(0, tangentA);
        this.setSurfaceRenderPoint(1, this.apex.renderOrigin);
        this.setSurfaceRenderPoint(2, tangentB);

        // render
        var elem = this.getSurfaceRenderElement(ctx, renderer);
        renderer.renderPath(ctx, elem, this.surfacePathCommands);
        renderer.stroke(ctx, elem, this.stroke, this.color, this.getLineWidth());
        renderer.fill(ctx, elem, this.fill, this.color);
        renderer.end(ctx, elem);
    };

    var svgURI = 'http://www.w3.org/2000/svg';

    Cone.prototype.getSurfaceRenderElement = function (ctx, renderer) {
        if (!renderer.isSvg) {
            return;
        }
        if (!this.surfaceSvgElement) {
            // create svgElement
            this.surfaceSvgElement = document.createElementNS(svgURI, 'path');
            this.surfaceSvgElement.setAttribute('stroke-linecap', 'round');
            this.surfaceSvgElement.setAttribute('stroke-linejoin', 'round');
        }
        return this.surfaceSvgElement;
    };

    Cone.prototype.setSurfaceRenderPoint = function (index, point) {
        var renderPoint = this.surfacePathCommands[index].renderPoints[0];
        renderPoint.set(point);
    };

    return Cone;

}));
/**
 * Box composite shape
 */

(function (root, factory) {
    // module definition
    if (typeof module == 'object' && module.exports) {
        // CommonJS
        module.exports = factory(require('./boilerplate'), require('./anchor'),
            require('./shape'), require('./rect'));
    } else {
        // browser global
        var Zdog = root.Zdog;
        Zdog.Box = factory(Zdog, Zdog.Anchor, Zdog.Shape, Zdog.Rect);
    }
}(this, function factory(utils, Anchor, Shape, Rect) {

// ----- BoxRect ----- //

    var BoxRect = Rect.subclass();
// prevent double-creation in parent.copyGraph()
// only create in Box.create()
    BoxRect.prototype.copyGraph = function () {
    };

// ----- Box ----- //

    var TAU = utils.TAU;
    var faceNames = [
        'frontFace',
        'rearFace',
        'leftFace',
        'rightFace',
        'topFace',
        'bottomFace',
    ];

    var boxDefaults = utils.extend({}, Shape.defaults);
    delete boxDefaults.path;
    faceNames.forEach(function (faceName) {
        boxDefaults[faceName] = true;
    });
    utils.extend(boxDefaults, {
        width: 1,
        height: 1,
        depth: 1,
        fill: true,
    });

    var Box = Anchor.subclass(boxDefaults);

    Box.prototype.create = function (options) {
        Anchor.prototype.create.call(this, options);
        this.updatePath();
        // HACK reset fill to trigger face setter
        this.fill = this.fill;
    };

    Box.prototype.updatePath = function () {
        // reset all faces to trigger setters
        faceNames.forEach(function (faceName) {
            this[faceName] = this[faceName];
        }, this);
    };

    faceNames.forEach(function (faceName) {
        var _faceName = '_' + faceName;
        Object.defineProperty(Box.prototype, faceName, {
            get: function () {
                return this[_faceName];
            },
            set: function (value) {
                this[_faceName] = value;
                this.setFace(faceName, value);
            },
        });
    });

    Box.prototype.setFace = function (faceName, value) {
        var rectProperty = faceName + 'Rect';
        var rect = this[rectProperty];
        // remove if false
        if (!value) {
            this.removeChild(rect);
            return;
        }
        // update & add face
        var options = this.getFaceOptions(faceName);
        options.color = typeof value == 'string' ? value : this.color;

        if (rect) {
            // update previous
            rect.setOptions(options);
        } else {
            // create new
            rect = this[rectProperty] = new BoxRect(options);
        }
        rect.updatePath();
        this.addChild(rect);
    };

    Box.prototype.getFaceOptions = function (faceName) {
        return {
            frontFace: {
                width: this.width,
                height: this.height,
                translate: {z: this.depth / 2},
            },
            rearFace: {
                width: this.width,
                height: this.height,
                translate: {z: -this.depth / 2},
                rotate: {y: TAU / 2},
            },
            leftFace: {
                width: this.depth,
                height: this.height,
                translate: {x: -this.width / 2},
                rotate: {y: -TAU / 4},
            },
            rightFace: {
                width: this.depth,
                height: this.height,
                translate: {x: this.width / 2},
                rotate: {y: TAU / 4},
            },
            topFace: {
                width: this.width,
                height: this.depth,
                translate: {y: -this.height / 2},
                rotate: {x: -TAU / 4},
            },
            bottomFace: {
                width: this.width,
                height: this.depth,
                translate: {y: this.height / 2},
                rotate: {x: TAU / 4},
            },
        }[faceName];
    };

// ----- set face properties ----- //

    var childProperties = ['color', 'stroke', 'fill', 'backface', 'front',
        'visible'];
    childProperties.forEach(function (property) {
        // use proxy property for custom getter & setter
        var _prop = '_' + property;
        Object.defineProperty(Box.prototype, property, {
            get: function () {
                return this[_prop];
            },
            set: function (value) {
                this[_prop] = value;
                faceNames.forEach(function (faceName) {
                    var rect = this[faceName + 'Rect'];
                    var isFaceColor = typeof this[faceName] == 'string';
                    var isColorUnderwrite = property == 'color' && isFaceColor;
                    if (rect && !isColorUnderwrite) {
                        rect[property] = value;
                    }
                }, this);
            },
        });
    });

    return Box;

}));
/**
 * Index
 */

(function (root, factory) {
    // module definition
    if (typeof module == 'object' && module.exports) {
        // CommonJS
        module.exports = factory(
            require('./boilerplate'),
            require('./canvas-renderer'),
            require('./svg-renderer'),
            require('./vector'),
            require('./anchor'),
            require('./dragger'),
            require('./illustration'),
            require('./path-command'),
            require('./shape'),
            require('./group'),
            require('./rect'),
            require('./rounded-rect'),
            require('./ellipse'),
            require('./polygon'),
            require('./hemisphere'),
            require('./cylinder'),
            require('./cone'),
            require('./box')
        );
    } else if (typeof define == 'function' && define.amd) {
        /* globals define */ // AMD
        define('zdog', [], root.Zdog);
    }
})(this, function factory(Zdog, CanvasRenderer, SvgRenderer, Vector, Anchor,
                          Dragger, Illustration, PathCommand, Shape, Group, Rect, RoundedRect,
                          Ellipse, Polygon, Hemisphere, Cylinder, Cone, Box) {

    Zdog.CanvasRenderer = CanvasRenderer;
    Zdog.SvgRenderer = SvgRenderer;
    Zdog.Vector = Vector;
    Zdog.Anchor = Anchor;
    Zdog.Dragger = Dragger;
    Zdog.Illustration = Illustration;
    Zdog.PathCommand = PathCommand;
    Zdog.Shape = Shape;
    Zdog.Group = Group;
    Zdog.Rect = Rect;
    Zdog.RoundedRect = RoundedRect;
    Zdog.Ellipse = Ellipse;
    Zdog.Polygon = Polygon;
    Zdog.Hemisphere = Hemisphere;
    Zdog.Cylinder = Cylinder;
    Zdog.Cone = Cone;
    Zdog.Box = Box;

    return Zdog;
});
